import Cesium from '../Ces/Cesium';
import Config from '../Core/Config';

function WeatherSystem(we) {
  this._we = we;
  this._snow = false;
  this._rain = false;
  this._fog = false;
  this._fogPostProcessStage = undefined;
  this._snowPostProcessStage = undefined;
  this._rainPostProcessStage = undefined;
  this._customPostProcessStage = undefined;
  this._rainAttribute = new Cesium.Matrix4(
    0.8/**/, 0.6/* cx */, 0.0, 0.0,
    0.8/**/, 0.7/* cy */, 0.0, 0.0,
    1.0/**/, 0.8/* cz */, 0.0, 0.0,
    0.5/* mix */, 0.0, 0.0, 0.0,
  );
  this._snowAttribute = new Cesium.Matrix4(
    1.0/**/, 0.0/* cFactor */, 0.0, 0.0,
    1.0/**/, 0.0/**/, 0.0, 0.0,
    1.0/**/, 0.0/**/, 0.0, 0.0,
    0.5/* mix */, 0.0, 0.0, 0.0,
  );

  this._snowLevel = WeatherSystem.Level.Normal;
  this._rainLevel = WeatherSystem.Level.Normal;
}

WeatherSystem.Level = {
  Slight: 0,
  Normal: 1,
  Heavy: 2,
};

Object.defineProperties(WeatherSystem.prototype, {
  snowLevel: {
    get() {
      return this._snowLevel;
    },
    set(value) {
      this._snowLevel = value;
      if (this._snowLevel === WeatherSystem.Level.Slight) {
        this._snowAttribute[3] = 0.2;
        this._snowAttribute[4] = 0.0;
      } else if (this._snowLevel === WeatherSystem.Level.Heavy) {
        this._snowAttribute[3] = 0.5;
        this._snowAttribute[4] = 1.0;
      } else {
        this._snowAttribute[3] = 0.5;
        this._snowAttribute[4] = 0.0;
      }
    },
  },
  rainLevel: {
    get() {
      return this._rainLevel;
    },
    set(value) {
      this._rainLevel = value;
      if (this._rainLevel === WeatherSystem.Level.Slight) {
        this._rainAttribute[0] = 0.5;
        this._rainAttribute[1] = 1.5;
        this._rainAttribute[3] = 0.2;
      } else if (this._rainLevel === WeatherSystem.Level.Heavy) {
        this._rainAttribute[0] = 2.0;
        this._rainAttribute[1] = 2.0;
        this._rainAttribute[3] = 0.5;
      } else {
        this._rainAttribute[0] = 0.8;
        this._rainAttribute[1] = 0.8;
        this._rainAttribute[3] = 0.4;
      }
    },
  },
  snow: {
    get() {
      return this._snow;
    },
    set(value) {
      if (value !== this._snow) {
        this._snow = value;
        if (value) {
          if (!Cesium.defined(this._snowPostProcessStage)) {
            this._snowPostProcessStage = this.createSnowPostProcessStage();
          }
          this._we.viewer.scene.postProcessStages.add(this._snowPostProcessStage);
        } else if (Cesium.defined(this._snowPostProcessStage)) {
          this._we.viewer.scene.postProcessStages.remove(this._snowPostProcessStage);
          this._snowPostProcessStage = undefined;
        }
      }
    },
  },
  rain: {
    get() {
      return this._rain;
    },
    set(value) {
      if (value !== this._rain) {
        this._rain = value;
        if (value) {
          if (!Cesium.defined(this._rainPostProcessStage)) {
            this._rainPostProcessStage = this.createRainPostProcessStage();
          }
          this._we.viewer.scene.postProcessStages.add(this._rainPostProcessStage);
        } else if (Cesium.defined(this._rainPostProcessStage)) {
          this._we.viewer.scene.postProcessStages.remove(this._rainPostProcessStage);
          this._rainPostProcessStage = undefined;
        }
      }
    },
  },
  fog: {
    get() {
      return this._fog;
    },
    set(value) {
      if (value !== this._fog) {
        this._fog = value;
        if (value) {
          if (!Cesium.defined(this._fogPostProcessStage)) {
            this._fogPostProcessStage = this.createFogPostProcessStage();
          }
          this._we.viewer.scene.postProcessStages.add(this._fogPostProcessStage);
        } else if (Cesium.defined(this._fogPostProcessStage)) {
          this._we.viewer.scene.postProcessStages.remove(this._fogPostProcessStage);
          this._fogPostProcessStage = undefined;
        }
      }
    },
  },
});

WeatherSystem.prototype.snowParticle = function () {
  const scene = this._we.viewer.scene;
  // snow
  const snowParticleSize = 12.0;
  const snowRadius = 100000.0;
  const minimumSnowImageSize = new Cesium.Cartesian2(
    snowParticleSize,
    snowParticleSize,
  );
  const maximumSnowImageSize = new Cesium.Cartesian2(
    snowParticleSize * 2.0,
    snowParticleSize * 2.0,
  );

  // eslint-disable-next-line prefer-const
  let snowSystem;

  let snowGravityScratch = new Cesium.Cartesian3();
  const snowUpdate = function (particle, dt) {
    snowGravityScratch = Cesium.Cartesian3.normalize(
      particle.position,
      snowGravityScratch,
    );
    Cesium.Cartesian3.multiplyByScalar(
      snowGravityScratch,
      Cesium.Math.randomBetween(-30.0, -300.0),
      snowGravityScratch,
    );
    particle.velocity = Cesium.Cartesian3.add(
      particle.velocity,
      snowGravityScratch,
      particle.velocity,
    );

    const distance = Cesium.Cartesian3.distance(
      scene.camera.position,
      particle.position,
    );

    if (distance > snowRadius) {
      particle.endColor.alpha = 0.0;
    } else {
      particle.endColor.alpha = snowSystem.endColor.alpha / (distance / snowRadius + 0.1);
    }
  };

  snowSystem = new Cesium.ParticleSystem({
    modelMatrix: Cesium.Matrix4.fromTranslation(
      scene.camera.position,
    ),
    minimumSpeed: -1.0,
    maximumSpeed: 0.0,
    lifetime: 15.0,
    emitter: new Cesium.SphereEmitter(snowRadius),
    startScale: 0.5,
    endScale: 1.0,
    image: Cesium.buildModuleUrl('Assets/Textures/snowflake_particle.png'),
    emissionRate: 700.0,
    startColor: Cesium.Color.WHITE.withAlpha(0.0),
    endColor: Cesium.Color.WHITE.withAlpha(1.0),
    minimumImageSize: minimumSnowImageSize,
    maximumImageSize: maximumSnowImageSize,
    updateCallback: snowUpdate,
  });
  // this._we.viewer.scene.primitives.add(snowSystem);

  // scene.skyAtmosphere.hueShift = -0.8;
  // scene.skyAtmosphere.saturationShift = -0.7;
  // scene.skyAtmosphere.brightnessShift = -0.33;

  // scene.fog.density = 0.001;
  // scene.fog.minimumBrightness = 0.8;

  this._snowSystem = snowSystem;
};


WeatherSystem.prototype.createSnowPostProcessStage = function () {
  const fragmentShaderSource = 'uniform sampler2D colorTexture;\n\
    uniform mat4 snowAttribute;\n\
    in vec2 v_textureCoordinates;\n\
    float snow(vec2 uv,float scale)\n\
    {\n\
        float timeFactor = snowAttribute[0][0];\n\
        float time = czm_frameNumber / 60.0 * timeFactor;\n\
        float w=smoothstep(1.,0.,-uv.y*(scale/10.0));if(w<.1)return 0.;\n\
        uv+=time/scale;uv.y+=time*2./scale;uv.x+=sin(uv.y+time*.5)/scale;\n\
        uv*=scale;vec2 s=floor(uv),f=fract(uv),p;float k=3.0,d;\n\
        p=0.5+0.35*sin(11.*fract(sin((s+p+scale)*mat2(7,3,6,5))*5.))-f;d=length(p);k=min(d,k);\n\
        k=smoothstep(0.0,k,sin(f.x+f.y)*0.01);\n\
        return k*w;\n\
    }\n\
    void main(){\n\
        float cFactor = snowAttribute[1][0];\n\
        float mFactor = snowAttribute[0][3];\n\
        vec2 resolution = czm_viewport.zw;\n\
        vec2 uv=(gl_FragCoord.xy*2.-resolution.xy)/min(resolution.x,resolution.y);\n\
        vec3 finalColor=vec3(0);\n\
        float c=smoothstep(1.,0.3,clamp(uv.y*.3+.8,0.,.75));\n\
        c *= cFactor;\n\
        c+=snow(uv,30.0)*cFactor;\n\
        c+=snow(uv,20.0)*cFactor;\n\
        c+=snow(uv,15.0)*cFactor;\n\
        c+=snow(uv,10.0);\n\
        c+=snow(uv,8.0);\n\
        c+=snow(uv,6.0);\n\
        c+=snow(uv,5.0);\n\
        finalColor=(vec3(c));\n\
        out_FragColor = mix(texture(colorTexture, v_textureCoordinates), vec4(finalColor,1), mFactor);\n\
    }';

  const postStage = new Cesium.PostProcessStage({
    fragmentShader: fragmentShaderSource,
    uniforms: {
      snowAttribute: this._snowAttribute,
    },
  });

  return postStage;
};

WeatherSystem.prototype.createRainPostProcessStage = function () {
  const fragmentShaderSource = 'uniform sampler2D colorTexture;\n\
        uniform mat4 rainAttribute;\n\
        in vec2 v_textureCoordinates;\n\
        float hash(float x){\n\
            return fract(sin(x*133.3)*13.13);\n\
        }\n\
        void main(void){\n\
            float timeFactor = rainAttribute[0][0];\n\
            float xFactor = rainAttribute[0][1];\n\
            float yFactor = rainAttribute[0][2];\n\
            float mFactor = rainAttribute[0][3];\n\
            float cx = rainAttribute[1][0];\n\
            float cy = rainAttribute[1][1];\n\
            float cz = rainAttribute[1][2];\n\
            float time = czm_frameNumber / 60.0 * timeFactor;\n\
            vec2 resolution = czm_viewport.zw;\n\
            vec2 uv= (gl_FragCoord.xy*2.0 - resolution.xy)/min(resolution.x,resolution.y);\n\
            vec3 c = vec3(cx,cy,cz);\n\
            float a = -0.4;\n\
            float si = sin(a),co=cos(a);\n\
            uv*= mat2(co,-si,si,co);\n\
            uv*= length(uv + vec2(0.0,4.9)) * 0.3 + 1.0;\n\
            float v = 1.0 - sin(hash(floor(uv.x*100.))*2.0);\n\
            float b = clamp(abs(sin(20.0*time*v + uv.y*(5.0/(2.0+v))))-.95,0.,1.)*20.;\n\
            v*=xFactor;\n\
            b*=yFactor;\n\
            c*=v*b;\n\
            out_FragColor = mix(texture(colorTexture, v_textureCoordinates), vec4(c,1), mFactor);\n\
        }';

  const postStage = new Cesium.PostProcessStage({
    fragmentShader: fragmentShaderSource,
    uniforms: {
      rainAttribute: this._rainAttribute,
    },
  });

  return postStage;
};

WeatherSystem.prototype.createFogPostProcessStage = function () {
  const fragmentShaderSource = 'float getDistance(sampler2D depthTexture, vec2 texCoords) \n'
    + '{ \n'
    + '    float depth = czm_unpackDepth(texture(depthTexture, texCoords)); \n'
    + '    if (depth == 0.0) { \n'
    + '        return czm_infinity; \n'
    + '    } \n'
    + '    vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); \n'
    + '    return -eyeCoordinate.z / eyeCoordinate.w; \n'
    + '} \n'
    + 'float interpolateByDistance(vec4 nearFarScalar, float distance) \n'
    + '{ \n'
    + '    float startDistance = nearFarScalar.x; \n'
    + '    float startValue = nearFarScalar.y; \n'
    + '    float endDistance = nearFarScalar.z; \n'
    + '    float endValue = nearFarScalar.w; \n'
    + '    float t = clamp((distance - startDistance) / (endDistance - startDistance), 0.0, 1.0); \n'
    + '    return mix(startValue, endValue, t); \n'
    + '} \n'
    + 'vec4 alphaBlend(vec4 sourceColor, vec4 destinationColor) \n'
    + '{ \n'
    + '    return sourceColor * vec4(sourceColor.aaa, 1.0) + destinationColor * (1.0 - sourceColor.a); \n'
    + '} \n'
    + 'uniform sampler2D colorTexture; \n'
    + 'uniform sampler2D depthTexture; \n'
    + 'uniform vec4 fogByDistance; \n'
    + 'uniform vec4 fogColor; \n'
    + 'in vec2 v_textureCoordinates; \n'
    + 'void main(void) \n'
    + '{ \n'
    + '    float distance = getDistance(depthTexture, v_textureCoordinates); \n'
    + '    vec4 sceneColor = texture(colorTexture, v_textureCoordinates); \n'
    + '    float blendAmount = interpolateByDistance(fogByDistance, distance); \n'
    + '    vec4 finalFogColor = vec4(fogColor.rgb, fogColor.a * blendAmount); \n'
    + '    out_FragColor = alphaBlend(finalFogColor, sceneColor); \n'
    + '} \n';

  const postStage = new Cesium.PostProcessStage({
    fragmentShader: fragmentShaderSource,
    uniforms: {
      fogByDistance: new Cesium.Cartesian4(10000, 0.0, 20000, 1.0),
      fogColor: Cesium.Color.WHITE,
    },
  });

  return postStage;
};

WeatherSystem.prototype.removeCustomPostProcessStage = function () {
  if (Cesium.defined(this._customPostProcessStage)) {
    this._we.viewer.scene.postProcessStages.remove(this._customPostProcessStage);
    this._customPostProcessStage = undefined;
  }
  if (Cesium.defined(this._videoElement)) {
    this._videoElement.remove();
    this._videoElement = undefined;
  }

  this._synchronizer = this._synchronizer && this._synchronizer.destroy();
  this._particleTexture = this._particleTexture && this._particleTexture.destroy();
};

WeatherSystem.prototype.addCustomPostProcessStage = function (videoUrl) {
  this.removeCustomPostProcessStage();

  const fragmentShaderSource = 'uniform sampler2D colorTexture; \n'
    + 'uniform sampler2D particleTexture; \n'
    + 'in vec2 v_textureCoordinates; \n'
    + 'void main(void) \n'
    + '{ \n'
    + '    vec2 resolution = czm_viewport.zw; \n'
    + '    vec2 uv = (gl_FragCoord.xy*2.0-resolution.xy)/min(resolution.x,resolution.y); \n'
    + '    float time = mod(czm_frameNumber,18.0) / 6.0; \n'
    + '    vec4 sceneColor = texture(colorTexture, v_textureCoordinates); \n'
    + '    //uv = v_textureCoordinates + vec2(1.0,0.0) * time; \n'
    + '    uv = v_textureCoordinates; \n'
    + '    vec2 uv2 = uv; \n'
    + '    //uv2.x+=floor(time)/3.0; \n'
    + '    vec4 particleColor = texture(particleTexture,uv2); \n'
    + '    out_FragColor = sceneColor; \n'
    + '    //if(particleColor.w > 0.0 && particleColor.r > 0.0 && particleColor.g > 0.0 && particleColor.b > 0.0) \n'
    + '    //if(particleColor.w > 0.0) \n'
    + '       out_FragColor = mix(sceneColor, particleColor, clamp(particleColor.g+particleColor.r+particleColor.b,0.0,1.0)); \n'
    + '} \n';

  const video = document.createElement('VIDEO');

  video.setAttribute('width', '320');
  video.setAttribute('height', '240');
  video.setAttribute('muted', '');
  video.setAttribute('autoplay', '');
  video.setAttribute('loop', '');
  video.setAttribute('crossorigin', '');
  video.setAttribute('controls', '');
  video.setAttribute('src', videoUrl);
  video.setAttribute('style', 'display:none');
  document.body.appendChild(video);
  // this._we.viewer.container.appendChild(video);
  const that = this;

  this._videoElement = video;

  // this._videoElement = document.getElementById("trailer2");
  const sampler = new Cesium.Sampler({
    minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
    magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
  });

  video.onplay = function () {
    if (!Cesium.defined(that._videoElement) || Cesium.defined(that._particleTexture)) {
      return;
    }
    that._particleTexture = new Cesium.Texture({
      context: that._we.viewer.scene.context,
      source: that._videoElement,
      sampler,
    });
    that._customPostProcessStage = new Cesium.PostProcessStage({
      fragmentShader: fragmentShaderSource,
      uniforms: {
        particleTexture: that._particleTexture,
      },
    });

    that._synchronizer = new Cesium.VideoSynchronizer({
      clock: that._we.viewer.clock,
      element: that._videoElement,
    });
    that._we.viewer.scene.postProcessStages.add(that._customPostProcessStage);
  };
};

WeatherSystem.prototype.clear = function () {
  this.removeCustomPostProcessStage();
  this.fog = false;
  this.rain = false;
  this.snow = false;
};

WeatherSystem.prototype.createRegionWeather = function (options) {
  const fsSnow = 'float snow(vec2 uv, float scale) { \n'
    + '    float time = czm_frameNumber / 60.0; \n'
    + '    float w = smoothstep(1., 0., -uv.y * (scale / 10.)); \n'
    + '    if (w < .1) \n'
    + '        return 0.; \n'
    + '    uv += time / scale; \n'
    + '    uv.y += time * 2. / scale; \n'
    + '    uv.x += sin(uv.y + time * .5) / scale; \n'
    + '    uv *= scale; \n'
    + '    vec2 s = floor(uv), f = fract(uv), p; \n'
    + '    float k = 3., d; \n'
    + '    p = .5 + .35 * sin(11. * fract(sin((s + p + scale) * mat2(7, 3, 6, 5)) * 5.)) - f; \n'
    + '    d = length(p); \n'
    + '    k = min(d, k); \n'
    + '    k = smoothstep(0., k, sin(f.x + f.y) * 0.01); \n'
    + '    return k * w; \n'
    + '    }'
      + 'czm_material czm_getMaterial(czm_materialInput materialInput) { \n'
      + '    float time = czm_frameNumber / 60.0; \n'
        + '    czm_material material = czm_getDefaultMaterial(materialInput); \n'
        + '    vec2 st = materialInput.st; \n'
        + '    vec4 color = vec4(0.); \n'
        + '    vec2 uv=st; \n'
        + '    vec3 finalColor = vec3(0);\n'
        + '    // float c=smoothstep(1.,0.3,clamp(uv.y*.3+.8,0.,.75));\n'
        + '    float c = 0.0;\n'
        + '    c += snow(uv, 30.) * .0;\n'
        + '    c += snow(uv, 20.) * .0;\n'
        + '    c += snow(uv, 15.) * .0;\n'
        + '    c += snow(uv, 10.);\n'
        + '    c += snow(uv, 8.);\n'
        + '    c += snow(uv, 6.);\n'
        + '    c += snow(uv, 5.);\n'
        + '    finalColor = (vec3(c));\n'
        + '    material.diffuse = finalColor; \n'
        + '    material.alpha = min(finalColor.r,min(finalColor.b,finalColor.g)); \n'
        + '    material.shininess = 1.0; \n' // Sharpen highlight
        + '    return material; \n'
        + '} \n';

  this._regionPrimitive = new Cesium.Primitive(options);
  this._regionPrimitive.appearance.material = new Cesium.Material({
    fabric: {
      source: fsSnow,
    },
  });
};


WeatherSystem.prototype.createRegionRain = function (options) {
  const fsRain = 'float hash(float x){ \n'
    + 'return fract(sin(x*133.3)*13.13);\n'
    + '}\n'
    + 'czm_material czm_getMaterial(czm_materialInput materialInput) { \n'
    + '    float time = czm_frameNumber / 60.0; \n'
      + '    czm_material material = czm_getDefaultMaterial(materialInput); \n'
      + '    vec2 st = materialInput.st; \n'
      + '    vec4 color = vec4(0.); \n'
      + '    vec2 uv=st; \n'
      + '    uv.x=1.0-uv.x; \n'
      + '    vec3 c=vec3(.6,.7,.8); \n'
      + '    float a=-.4; \n'
      + '    float si=sin(a),co=cos(a); \n'
      + '    uv*=mat2(co,-si,si,co); \n'
      + '    uv*=length(uv+vec2(0,4.9))*.3+1.; \n'
      + '    float v=1.-sin(hash(floor(uv.x*100.))*2.); \n'
      + '    float b=clamp(abs(sin(20.*time*v+uv.y*(5./(2.+v))))-.95,0.,1.)*20.; \n'
      + '    c*=v*b; \n'
      + '    color = vec4(c,1.0); \n'
      + '    material.diffuse = vec3(1.0); \n'
      + '    material.alpha = min(color.r,min(color.b,color.g)); \n'
      + '    material.shininess = 1.0; \n' // Sharpen highlight
      + '    return material; \n'
      + '} \n';

  this._regionRainPrimitive = new Cesium.Primitive(options);
  this._regionRainPrimitive.appearance.material = new Cesium.Material({
    fabric: {
      source: fsRain,
    },
  });
};

WeatherSystem.prototype.update = function (frameState) {
  if (Cesium.defined(this._snowSystem)) {
    this._snowSystem.modelMatrix = Cesium.Matrix4.fromTranslation(
      this._we.viewer.scene.camera.position,
    );
    this._snowSystem.update(frameState);
  }

  if (Cesium.defined(this._particleTexture) && Cesium.defined(this._videoElement)) {
    if (this._videoElement.readyState >= 2) {
      this._particleTexture.copyFrom({
        source:this._videoElement
      });
    }
  }

  if (Cesium.defined(this._regionRainPrimitive)) {
    this._regionRainPrimitive.update(frameState);
  }
  if (Cesium.defined(this._regionPrimitive)) {
    this._regionPrimitive.update(frameState);
  }
};

WeatherSystem.prototype.isDestroyed = function () {
  return false;
};

WeatherSystem.prototype.destroy = function () {
  this.removeCustomPostProcessStage();

  return Cesium.destroyObject(this);
};

export default WeatherSystem;
